/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Base class for FBO tests.
 *//*--------------------------------------------------------------------*/

#include "es31fFboTestCase.hpp"
#include "es31fFboTestUtil.hpp"
#include "tcuTestLog.hpp"
#include "tcuImageCompare.hpp"
#include "tcuRenderTarget.hpp"
#include "sglrGLContext.hpp"
#include "sglrReferenceContext.hpp"
#include "gluStrUtil.hpp"
#include "gluContextInfo.hpp"
#include "deRandom.hpp"
#include "glwEnums.hpp"
#include "glwFunctions.hpp"

#include <algorithm>

namespace deqp
{
namespace gles31
{
namespace Functional
{

using tcu::TestLog;
using std::string;

FboTestCase::FboTestCase (Context& context, const char* name, const char* description, bool useScreenSizedViewport)
	: TestCase			(context, name, description)
	, m_viewportWidth	(useScreenSizedViewport ? context.getRenderTarget().getWidth() : 128)
	, m_viewportHeight	(useScreenSizedViewport ? context.getRenderTarget().getHeight() : 128)
{
}

FboTestCase::~FboTestCase (void)
{
}

FboTestCase::IterateResult FboTestCase::iterate (void)
{
	glu::RenderContext&			renderCtx		= TestCase::m_context.getRenderContext();
	const tcu::RenderTarget&	renderTarget	= renderCtx.getRenderTarget();
	TestLog&					log				= m_testCtx.getLog();

	// Viewport.
	de::Random					rnd				(deStringHash(getName()));
	int							width			= deMin32(renderTarget.getWidth(),	m_viewportWidth);
	int							height			= deMin32(renderTarget.getHeight(),	m_viewportHeight);
	int							x				= rnd.getInt(0, renderTarget.getWidth()		- width);
	int							y				= rnd.getInt(0, renderTarget.getHeight()	- height);

	// Surface format and storage is choosen by render().
	tcu::Surface				reference;
	tcu::Surface				result;

	// Call preCheck() that can throw exception if some requirement is not met.
	preCheck();

	log << TestLog::Message << "Rendering with GL driver" << TestLog::EndMessage;

	// Render using GLES3.1
	try
	{
		sglr::GLContext context(renderCtx, log, 0, tcu::IVec4(x, y, width, height));
		setContext(&context);
		render(result);

		// Check error.
		deUint32 err = glGetError();
		if (err != GL_NO_ERROR)
			throw glu::Error(err, glu::getErrorStr(err).toString().c_str(), DE_NULL, __FILE__, __LINE__);

		setContext(DE_NULL);
	}
	catch (const FboTestUtil::FboIncompleteException& e)
	{
		if (e.getReason() == GL_FRAMEBUFFER_UNSUPPORTED)
		{
			log << e;
			m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Not supported");
			return STOP;
		}
		else
			throw;
	}

	log << TestLog::Message << "Rendering reference image" << TestLog::EndMessage;

	// Render reference.
	{
		sglr::ReferenceContextBuffers	buffers	(tcu::PixelFormat(8,8,8,renderTarget.getPixelFormat().alphaBits?8:0), renderTarget.getDepthBits(), renderTarget.getStencilBits(), width, height);
		sglr::ReferenceContext			context	(sglr::ReferenceContextLimits(renderCtx), buffers.getColorbuffer(), buffers.getDepthbuffer(), buffers.getStencilbuffer());

		setContext(&context);
		render(reference);
		setContext(DE_NULL);
	}

	bool isOk = compare(reference, result);
	m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
							isOk ? "Pass"				: "Image comparison failed");
	return STOP;
}

bool FboTestCase::compare (const tcu::Surface& reference, const tcu::Surface& result)
{
	return tcu::fuzzyCompare(m_testCtx.getLog(), "Result", "Image comparison result", reference, result, 0.05f, tcu::COMPARE_LOG_RESULT);
}

void FboTestCase::readPixels (tcu::Surface& dst, int x, int y, int width, int height, const tcu::TextureFormat& format, const tcu::Vec4& scale, const tcu::Vec4& bias)
{
	FboTestUtil::readPixels(*getCurrentContext(), dst, x, y, width, height, format, scale, bias);
}

void FboTestCase::readPixels (tcu::Surface& dst, int x, int y, int width, int height)
{
	getCurrentContext()->readPixels(dst, x, y, width, height);
}

void FboTestCase::checkFramebufferStatus (deUint32 target)
{
	deUint32 status = glCheckFramebufferStatus(target);
	if (status != GL_FRAMEBUFFER_COMPLETE)
		throw FboTestUtil::FboIncompleteException(status, __FILE__, __LINE__);
}

void FboTestCase::checkError (void)
{
	deUint32 err = glGetError();
	if (err != GL_NO_ERROR)
		throw glu::Error((int)err, (string("Got ") + glu::getErrorStr(err).toString()).c_str(), DE_NULL, __FILE__, __LINE__);
}

static bool isRequiredFormat (deUint32 format, glu::RenderContext& renderContext)
{
	const bool supportsES32 = glu::contextSupports(renderContext.getType(), glu::ApiType::es(3, 2));
	const bool supportsGL45 = glu::contextSupports(renderContext.getType(), glu::ApiType::core(4, 5));
	switch (format)
	{
		// Color-renderable formats
		case GL_RGBA32I:
		case GL_RGBA32UI:
		case GL_RGBA16I:
		case GL_RGBA16UI:
		case GL_RGBA8:
		case GL_RGBA8I:
		case GL_RGBA8UI:
		case GL_SRGB8_ALPHA8:
		case GL_RGB10_A2:
		case GL_RGB10_A2UI:
		case GL_RGBA4:
		case GL_RGB5_A1:
		case GL_RGB8:
		case GL_RGB565:
		case GL_RG32I:
		case GL_RG32UI:
		case GL_RG16I:
		case GL_RG16UI:
		case GL_RG8:
		case GL_RG8I:
		case GL_RG8UI:
		case GL_R32I:
		case GL_R32UI:
		case GL_R16I:
		case GL_R16UI:
		case GL_R8:
		case GL_R8I:
		case GL_R8UI:
			return true;

		// Depth formats
		case GL_DEPTH_COMPONENT32F:
		case GL_DEPTH_COMPONENT24:
		case GL_DEPTH_COMPONENT16:
			return true;

		// Depth+stencil formats
		case GL_DEPTH32F_STENCIL8:
		case GL_DEPTH24_STENCIL8:
			return true;

		// Stencil formats
		case GL_STENCIL_INDEX8:
			return true;

		// Float format
		case GL_RGBA32F:
		case GL_R11F_G11F_B10F:
		case GL_RG32F:
		case GL_R32F:
		case GL_RGBA16F:
		case GL_RG16F:
		case GL_R16F:
			return supportsES32 || supportsGL45;
		case GL_RGB16F:
			return supportsGL45;

		default:
			return false;
	}
}

static std::vector<std::string> getEnablingExtensions (deUint32 format, glu::RenderContext& renderContext)
{
	const bool					supportsES32 = glu::contextSupports(renderContext.getType(), glu::ApiType::es(3, 2));
	std::vector<std::string>	out;

	DE_ASSERT(!isRequiredFormat(format, renderContext));

	switch (format)
	{
		case GL_RGB16F:
			out.push_back("GL_EXT_color_buffer_half_float");
			break;

		case GL_RGB32F:
			out.push_back("GL_EXT_color_buffer_float");
			break;

		case GL_RGBA16F:
		case GL_RG16F:
		case GL_R16F:
			if (!supportsES32)
				out.push_back("GL_EXT_color_buffer_half_float");
			break;

		case GL_RGBA32F:
		case GL_R11F_G11F_B10F:
		case GL_RG32F:
		case GL_R32F:
			if (!supportsES32)
				out.push_back("GL_EXT_color_buffer_float");
			break;

		case GL_R16:
		case GL_RG16:
		case GL_RGBA16:
			out.push_back("GL_EXT_texture_norm16");
			break;

		default:
			break;
	}

	return out;
}

static bool isAnyExtensionSupported (Context& context, const std::vector<std::string>& requiredExts)
{
	for (std::vector<std::string>::const_iterator iter = requiredExts.begin(); iter != requiredExts.end(); iter++)
	{
		const std::string& extension = *iter;

		if (context.getContextInfo().isExtensionSupported(extension.c_str()))
			return true;
	}

	return false;
}

void FboTestCase::checkFormatSupport (deUint32 sizedFormat)
{
	const bool						isCoreFormat	= isRequiredFormat(sizedFormat, m_context.getRenderContext());
	const std::vector<std::string>	requiredExts	= (!isCoreFormat) ? getEnablingExtensions(sizedFormat, m_context.getRenderContext()) : std::vector<std::string>();

	// Check that we don't try to use invalid formats.
	DE_ASSERT(isCoreFormat || !requiredExts.empty());

	if (!requiredExts.empty() && !isAnyExtensionSupported(m_context, requiredExts))
		throw tcu::NotSupportedError("Format not supported");
}

static int getMinimumSampleCount (deUint32 format)
{
	switch (format)
	{
		// Core formats
		case GL_RGBA32I:
		case GL_RGBA32UI:
		case GL_RGBA16I:
		case GL_RGBA16UI:
		case GL_RGBA8:
		case GL_RGBA8I:
		case GL_RGBA8UI:
		case GL_SRGB8_ALPHA8:
		case GL_RGB10_A2:
		case GL_RGB10_A2UI:
		case GL_RGBA4:
		case GL_RGB5_A1:
		case GL_RGB8:
		case GL_RGB565:
		case GL_RG32I:
		case GL_RG32UI:
		case GL_RG16I:
		case GL_RG16UI:
		case GL_RG8:
		case GL_RG8I:
		case GL_RG8UI:
		case GL_R32I:
		case GL_R32UI:
		case GL_R16I:
		case GL_R16UI:
		case GL_R8:
		case GL_R8I:
		case GL_R8UI:
		case GL_DEPTH_COMPONENT32F:
		case GL_DEPTH_COMPONENT24:
		case GL_DEPTH_COMPONENT16:
		case GL_DEPTH32F_STENCIL8:
		case GL_DEPTH24_STENCIL8:
		case GL_STENCIL_INDEX8:
			return 4;

		// GL_EXT_color_buffer_float
		case GL_R11F_G11F_B10F:
		case GL_RG16F:
		case GL_R16F:
			return 4;

		case GL_RGBA32F:
		case GL_RGBA16F:
		case GL_RG32F:
		case GL_R32F:
			return 0;

		// GL_EXT_color_buffer_half_float
		case GL_RGB16F:
			return 0;

		default:
			DE_FATAL("Unknown format");
			return 0;
	}
}

static std::vector<int> querySampleCounts (const glw::Functions& gl, deUint32 format)
{
	int					numSampleCounts		= 0;
	std::vector<int>	sampleCounts;

	gl.getInternalformativ(GL_RENDERBUFFER, format, GL_NUM_SAMPLE_COUNTS, 1, &numSampleCounts);

	if (numSampleCounts > 0)
	{
		sampleCounts.resize(numSampleCounts);
		gl.getInternalformativ(GL_RENDERBUFFER, format, GL_SAMPLES, (glw::GLsizei)sampleCounts.size(), &sampleCounts[0]);
	}

	GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to query sample counts for format");

	return sampleCounts;
}

void FboTestCase::checkSampleCount (deUint32 sizedFormat, int numSamples)
{
	const int minSampleCount = getMinimumSampleCount(sizedFormat);

	if (numSamples > minSampleCount)
	{
		// Exceeds spec-mandated minimum - need to check.
		const std::vector<int> supportedSampleCounts = querySampleCounts(m_context.getRenderContext().getFunctions(), sizedFormat);

		if (std::find(supportedSampleCounts.begin(), supportedSampleCounts.end(), numSamples) == supportedSampleCounts.end())
			throw tcu::NotSupportedError("Sample count not supported");
	}
}

void FboTestCase::clearColorBuffer (const tcu::TextureFormat& format, const tcu::Vec4& value)
{
	FboTestUtil::clearColorBuffer(*getCurrentContext(), format, value);
}

} // Functional
} // gles31
} // deqp
